package per.chenyl.framework.persistence.mapper.impl;


import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import per.chenyl.framework.persistence.common.constant.PersistenceConstant;
import per.chenyl.framework.persistence.common.util.ClassUtil;
import per.chenyl.framework.persistence.common.util.CommonUtil;
import per.chenyl.framework.persistence.entity.anno.*;
import per.chenyl.framework.persistence.entity.exception.FieldValidateException;
import per.chenyl.framework.persistence.entity.exception.QueryException;
import per.chenyl.framework.persistence.mapper.BaseMapper;
import per.chenyl.framework.persistence.wrapper.BatchDeleteWrapper;
import per.chenyl.framework.persistence.wrapper.Wrapper;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.ResultSetMetaData;
import java.util.*;

@Component
public class BaseMapperImpl<T> implements BaseMapper<T> {
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Value(value = "${spring.jdbc.plus.validate}")
    private boolean validate = true;

    @Override
    public boolean insert(T entity, String... schema) {
        if (Objects.isNull(entity)) {
            throw new RuntimeException("插入数据不可为空！");
        }
        return batchInsert(Collections.singletonList(entity), schema);
    }


    @Override
    public boolean batchInsert(List<T> entitys, String... schema) {
        validateBatchOperateData(entitys);

        SortedColsValues sortedColsValues = getSortedColsAndBatchUpdateValues(entitys, null, false);

        final String tableName = handldSchema(schema, ClassUtil.getTableName(entitys.get(0).getClass()));
        final StringBuilder firstSqlBuilder = new StringBuilder();
        firstSqlBuilder.append("insert into ").append(tableName).append(" (");
        sortedColsValues.cols.forEach(col -> {
            firstSqlBuilder.append(col).append(",");
        });
        final String firstSql = firstSqlBuilder.substring(0, firstSqlBuilder.length() - 1);

        final StringBuilder secondSqlBuilder = new StringBuilder();
        secondSqlBuilder.append(firstSql);
        secondSqlBuilder.append(") values (");
        sortedColsValues.cols.forEach(value -> {
            secondSqlBuilder.append("?,");
        });
        final String secondSql = secondSqlBuilder.substring(0, secondSqlBuilder.length() - 1) + ");";
        try {
            jdbcTemplate.batchUpdate(secondSql, sortedColsValues.batchArgs);
        } catch (DataAccessException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        return true;
    }


    @Override
    public boolean update(T entity, Wrapper<T> wrapper, String... schema) {
        return batchUpdate(Collections.singletonList(entity), wrapper, schema);
    }

    @Override
    public boolean batchUpdate(List<T> entitys, Wrapper<T> wrapper, String... schema) {
        validateBatchOperateData(entitys);
        SortedColsValues sortedColsValues = getSortedColsAndBatchUpdateValues(entitys, wrapper,true);
        Class<T> tClass = getEntityType();
        StringBuilder sb = new StringBuilder();
        sb.append("update ").append(handldSchema(schema, ClassUtil.getTableName(tClass))).append(" set ");
        sortedColsValues.cols.forEach(key -> {
            sb.append(key).append("=?,");
        });
        final String firstSql = sb.substring(0, sb.length() - 1) + " ";
        final String secondSql = firstSql + wrapper.getCondition();
        sb.delete(0, sb.length());
        sb.append(secondSql);
        sortedColsValues.idCols.forEach(idCol -> {
            sb.append(" and ").append(idCol).append("=?");
        });
        try {
            jdbcTemplate.batchUpdate(sb.toString(), sortedColsValues.batchArgs);
        } catch (DataAccessException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        return false;
    }

    private String handldSchema(String[] schema, String tableName) {
        return CommonUtil.isNotEmptyArray(schema) ? schema[0] + "." + tableName : tableName;
    }

    @Override
    public T selectOne(Wrapper<T> wrapper, String... schema) {
        Objects.requireNonNull(wrapper, "Wrapper 不可为空！");
        List<T> list = selectAll(wrapper);
        return CommonUtil.isNotEmptyCollection(list) ? list.get(0) : null;
    }

    @Override
    public List<T> selectAll(Wrapper<T> wrapper, String... schema) {
        Objects.requireNonNull(wrapper, "Wrapper 不可为空！");
        String sql;
        StringBuilder querySqlBuilder = new StringBuilder();
        Class<T> cl = getEntityType();
        if (wrapper.isUseSelect()) {
            querySqlBuilder.append(wrapper.getCondition());
            sql = querySqlBuilder.toString().replace(PersistenceConstant.TABLE_PLACEHOLDER, ClassUtil.getTableName(cl));
        } else {
            sql = querySqlBuilder.append("select * from ").append(handldSchema(schema, ClassUtil.getTableName(cl))).append(" ").append(wrapper.getCondition()).toString();

        }
        return queryForList(sql, cl, wrapper.getValues());
    }

    @Override
    public void delete(Wrapper<T> wrapper, String... schema) {
        Objects.requireNonNull(wrapper, "Wrapper 不可为空！");
        if (wrapper instanceof BatchDeleteWrapper) {
            throw new UnsupportedOperationException("请调用批量删除方法[batchDelete()]！");
        }
        StringBuilder builder = new StringBuilder();
        Class<T> cl = getEntityType();
        builder.append("delete from ").append(handldSchema(schema, ClassUtil.getTableName(cl))).append(" ");
        builder.append(wrapper.getCondition());
        try {
            jdbcTemplate.update(builder.toString(), wrapper.getValues());
        } catch (DataAccessException e) {
            throw new RuntimeException("删除数据失败！", e);
        }
    }

    @Override
    public void batchDelete(Wrapper<T> wrapper, String... schema) {
        Objects.requireNonNull(wrapper, "Wrapper 不可为空！");
        if (!(wrapper instanceof BatchDeleteWrapper)) {
            throw new UnsupportedOperationException("batchDelete()方法必须传入[BatchDeleteWrapper]构造器！");
        }
        StringBuilder builder = new StringBuilder();
        Class<T> cl = getEntityType();
        builder.append("delete from ").append(handldSchema(schema, ClassUtil.getTableName(cl))).append(" ").append(wrapper.getCondition())
                .append(wrapper.getBatchSql());
        try {
            final Object[] conditionArgs = wrapper.getValues();
            final Object[] batchDeleteValues = wrapper.getBatchDeleteValues();
            final Object[] args = new Object[conditionArgs.length + batchDeleteValues.length];
            System.arraycopy(conditionArgs, 0, args, 0, conditionArgs.length);
            System.arraycopy(batchDeleteValues, 0, args, conditionArgs.length, batchDeleteValues.length);
            jdbcTemplate.update(builder.toString(), args);
        } catch (DataAccessException e) {
            throw new RuntimeException("删除数据失败！", e);
        }

    }

    private Map<String, String> getTableColumusAndPropertiesByEntity(Class cl) {
        Map<String, String> colPropMap = new HashMap<>();
        if (!cl.isAnnotationPresent(Entity.class)) {
            throw new RuntimeException("Entity [" + cl.getName() + "] 必须持有注解 [" + Entity.class.getName() + "]");
        }
        for (; cl != Object.class && (cl.isAnnotationPresent(Entity.class) || cl.isAnnotationPresent(BaseEntity.class)); cl = cl.getSuperclass()) {
            Field[] fields = cl.getDeclaredFields();
            for (Field field : fields) {
                boolean annotationPresent = field.isAnnotationPresent(Column.class);
                if (annotationPresent) {
                    final Column columnAnno = field.getAnnotation(Column.class);
                    final String annoValue = columnAnno.value();
                    if (CommonUtil.getNonNullString(annoValue).length() > 1) {
                        colPropMap.put(annoValue.length() > 0 ? annoValue : field.getName(), field.getName());
                    }
                } else {
                    colPropMap.put(field.getName(), field.getName());

                }
            }
        }
        if (colPropMap.isEmpty()) {
            throw new RuntimeException("Entity [" + cl.getName() + "] 持有的数据库字段为空");
        }
        return colPropMap;
    }


    private List<T> queryForList(String sql, Class<T> clazz, Object... params) {
        final List<T> result = new ArrayList<>();
        jdbcTemplate.query(sql, params, rs -> {
            try {
                // 字段名称
                List<String> columnNames = new ArrayList<>();
                ResultSetMetaData meta = rs.getMetaData();
                int num = meta.getColumnCount();
                for (int i = 0; i < num; i++) {
                    columnNames.add(meta.getColumnLabel(i + 1));
                }
                // 设置值
                do {
                    T obj = clazz.newInstance();
                    Map<String, String> columusAndProps = getTableColumusAndPropertiesByEntity(clazz);
                    for (int i = 0; i < num; i++) {
                        // 获取值
                        Object value = rs.getObject(i + 1);
                        if (Objects.isNull(value)) {
                            continue;
                        }
                        // table.column形式的字段去掉前缀table.
                        String columnName = columnNames.get(i);
                        String property = columusAndProps.get(columnName);
                        if (Objects.isNull(property)) {
                            throw new RuntimeException("实体类和数据库[" + columnName + "]字段不一致！");
                        }
                        // 复制值到属性，这是spring的工具类
                        BeanUtils.copyProperty(obj, property, value);
                    }
                    result.add(obj);
                } while (rs.next());
            } catch (Exception e) {
                throw new QueryException(e);
            }
        });
        return result;
    }

    @SuppressWarnings("unchecked")
    private Class<T> getEntityType() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return (Class<T>) parameterizedType.getActualTypeArguments()[0];
        }
        throw new RuntimeException("获取 Entity类型失败！");
    }

    private void validateBatchOperateData(List<T> entitys) {
        if (!CommonUtil.isNotEmptyCollection(entitys)) {
            throw new RuntimeException("批量操作数据集合不可为空！");
        }
        Set<String> clNames = new HashSet<>();
        entitys.forEach(i -> {
            boolean isEntity = i.getClass().isAnnotationPresent(Entity.class);
            String clName = i.getClass().getName();
            if (!isEntity) {
                throw new RuntimeException("Entity [" + clName + "] 必须持有注解 [" + Entity.class.getName() + "]");
            }
            clNames.add(clName);

        });
        if (clNames.size() > 1) {
            throw new RuntimeException("批量操作的所有对象必须对应一个数据表");
        }
    }

    private class SortedColsValues {
        List<String> cols;
        List<Object[]> batchArgs;
        List<String> idCols;


        SortedColsValues(List<String> cols, List<Object[]> batchArgs, List<String> idCols) {
            this.cols = cols;
            this.batchArgs = batchArgs;
            this.idCols = idCols;
        }
    }

    @SuppressWarnings("unchecked")
    private SortedColsValues getSortedColsAndBatchUpdateValues(List<T> entitys, Wrapper<T> wrapper, boolean update) {
        List<String> notIdCols = new ArrayList(16);
        List<String> idCols = new ArrayList(16);
        List<Object[]> allArgs = new ArrayList<>();
        for (int i = 0; i < entitys.size(); i++) {
            T entity = entitys.get(i);
            List<Object> perNotIdArgs = new ArrayList<>();
            List<Object> perIdArgs = new ArrayList<>();
            for (Class<?> cl = entity.getClass(); cl != Object.class && (cl.isAnnotationPresent(Entity.class) ||
                    cl.isAnnotationPresent(BaseEntity.class)); cl = cl.getSuperclass()) {
                final Field[] fields = cl.getDeclaredFields();
                for (Field field : fields) {
                    boolean holdColumnAnno = field.isAnnotationPresent(Column.class);
                    if (holdColumnAnno) {
                        final Column columnAnno = field.getAnnotation(Column.class);
                        if (columnAnno.exclude()) {
                            continue;
                        }
                    }

                    //验证
                    field.setAccessible(true);
                    Object o = null;
                    try {
                        o = field.get(entity);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    validateValue(field,o);
                    //如果没有注解，就直接使用字段名作为columun name，否则看注解值，如果为空还是去字段名为column name否则就去注解value值
                    if (i == 0) {
                        if (holdColumnAnno) {
                            final Column columnAnno = field.getAnnotation(Column.class);
                            final String annoValue = columnAnno.value();
                            //拥有Id注解，跳过
                            if (update && field.isAnnotationPresent(Id.class)) {
                                idCols.add(annoValue.length() > 0 ? annoValue : field.getName());
                            } else {
                                notIdCols.add(annoValue.length() > 0 ? annoValue : field.getName());
                            }
                        } else {
                            if (update && field.isAnnotationPresent(Id.class)) {
                                idCols.add(field.getName());
                            } else {
                                notIdCols.add(field.getName());
                            }
                        }
                    }
                    if (update && field.isAnnotationPresent(Id.class)) {
                        perIdArgs.add(o);
                    } else {
                        perNotIdArgs.add(o);
                    }
                }
            }

            perNotIdArgs.addAll(perIdArgs);
            if (update) {
                perNotIdArgs.addAll(Arrays.asList(wrapper.getValues()));
            }
            allArgs.add(perNotIdArgs.toArray());
        }
        return new SortedColsValues(notIdCols, allArgs, idCols);
    }

    /**
     * 字段值验证
     * @param field 字段属性
     * @param value 字段值
     */
    private void validateValue(Field field, Object value) {
        if (validate) {
            if (field.isAnnotationPresent(Notnull.class) && Objects.isNull(value)) {
                Notnull notnull = field.getAnnotation(Notnull.class);
                throw new NullPointerException(notnull.message());
            }
            if (field.isAnnotationPresent(NotEmpty.class) && (Objects.isNull(value) || String.valueOf(value).length() == 0)) {
                NotEmpty notEmpty = field.getAnnotation(NotEmpty.class);
                throw new RuntimeException(notEmpty.message());
            }

            if (field.isAnnotationPresent(Pattern.class)) {
                Pattern pattern = field.getAnnotation(Pattern.class);
                if (!String.valueOf(value).matches(pattern.regex())) {
                    throw new FieldValidateException(pattern.message());
                }
            }
            if (field.isAnnotationPresent(MaxLength.class)) {
                MaxLength maxAnno = field.getAnnotation(MaxLength.class);
                if (String.valueOf(value).length() > maxAnno.value()) {
                    throw new FieldValidateException(maxAnno.message());
                }
            }
        }
    }
}